/*
 *  Copyright 2019-2020 Zheng Jie
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.yuecai.modules.security.rest;

import cn.hutool.core.util.IdUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wf.captcha.base.Captcha;
import com.yuecai.annotation.rest.AnonymousDeleteMapping;
import com.yuecai.annotation.rest.AnonymousGetMapping;
import com.yuecai.config.RsaProperties;
import com.yuecai.config.YcConstants;
import com.yuecai.config.limit.RateLimitService;
import com.yuecai.exception.BadRequestException;
import com.yuecai.modules.enums.SendCodeEnum;
import com.yuecai.modules.security.JwtUserDto;
import com.yuecai.modules.security.config.bean.LoginProperties;
import com.yuecai.modules.security.config.bean.SecurityProperties;
import com.yuecai.modules.security.security.TokenProvider;
import com.yuecai.modules.security.service.OnlineUserService;
import com.yuecai.modules.security.service.SmsCodeAuthenticationToken;
import com.yuecai.modules.security.service.dto.AuthUserDto;
import com.yuecai.modules.yc_user.service.YcUserService;
import com.yuecai.modules.yc_user.service.dto.PhoneCode;
import com.yuecai.modules.yc_user.service.dto.UserUpdateDto;
import com.yuecai.modules.yc_user.service.dto.YcUserDto;
import com.yuecai.modules.yc_user.service.dto.YcUserRegisterDto;
import com.yuecai.utils.RedisUtils;
import com.yuecai.utils.RsaUtils;
import com.yuecai.utils.SNUtils;
import com.yuecai.utils.SecurityUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author Zheng Jie
 * @date 2018-11-23
 * 授权、根据token获取用户详细信息
 */
@Slf4j
@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
@Api(tags = "系统：系统授权接口")
public class YcUserController {
    private final SecurityProperties properties;
    private final RedisUtils redisUtils;
    private final OnlineUserService onlineUserService;
    private final TokenProvider tokenProvider;
    private final AuthenticationManagerBuilder authenticationManagerBuilder;
    private final ObjectMapper objectMapper;
    private final AuthenticationManager authenticationManager;
    @Resource
    private LoginProperties loginProperties;

    @Autowired
    private YcUserService ycUserService;

    @Autowired
    private RateLimitService rateLimitService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    @GetMapping("/{distributionId}")
    public ResponseEntity<Object> query(@PathVariable Long distributionId){
        YcUserDto byId = ycUserService.findById(distributionId);

        return new ResponseEntity<>(byId,HttpStatus.OK);
    }

    @ApiOperation("注册发送验证码")
    @GetMapping(value = "/api/send-code")
    public ResponseEntity<Object> sendCode(@RequestParam String phone,@RequestParam Integer type,HttpServletRequest request) throws Exception {
        SendCodeEnum[] values = SendCodeEnum.values();

        String ip = request.getRemoteAddr();
        //限流
        boolean reached = rateLimitService.reached(phone);
        boolean ipflg = rateLimitService.reached(ip);
        if (reached && ipflg){
            throw new Exception("一分钟只能发送一次！");
        }
        for (SendCodeEnum value : values) {
            Integer type1 = value.getType();
            if (type1==type){
                String code = SNUtils.genSMSCode();
                String key = phone + "_" + value.getVal();
                stringRedisTemplate.opsForValue().set(key,code, Duration.ofMinutes(YcConstants.EXPIRY_CAPTCHA_TIME));
                //TODO 发送验证码
               /* HashMap<String, String> map = new HashMap<>();
                map.put("code",code);
                String s = objectMapper.writeValueAsString(map);*/
                SNUtils.sendSMSCodeTencent(phone,code);
                return ResponseEntity.ok().body("发送成功");
            }
        }
        throw new Exception("类型不正确！");
    }



    @ApiOperation("检验")
    @PostMapping(value = "/api/check-code")
    public ResponseEntity<Boolean> checkOutCode(@Validated @RequestBody PhoneCode phoneD) throws Exception {
        String phone = phoneD.getPhone();
        Integer type = phoneD.getType();
        for (SendCodeEnum value : SendCodeEnum.values()) {
            Integer type1 = value.getType();
            if (type1 == type) {
                String key = phone + "_" + value.getVal();

                String s1 = stringRedisTemplate.opsForValue().get(key);
                stringRedisTemplate.delete(key);
                if (phoneD.getCode().equals(s1)) {
                    return ResponseEntity.ok(true);
                }
                return ResponseEntity.ok().body(false);
            }
        }
        throw new Exception("类型不正确！");
    }


    @ApiOperation("注册")
    @PostMapping(value = "/api/register")
    public ResponseEntity<Object> register(@RequestBody YcUserRegisterDto dto) throws Exception {
        YcUserDto register = null;
        try {
            register = ycUserService.register(dto);
        } catch (DataIntegrityViolationException e) {
            e.printStackTrace();
            log.error("该手机号已经被注册！");
            throw new Exception("该手机号已经被注册！");
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
        return ResponseEntity.ok(register);
    }


    @ApiOperation("修改用户信息")
    @PutMapping(value = "/user")
    public ResponseEntity<Object> putUser(@RequestBody UserUpdateDto dto) throws Exception {
        String currentUsername = SecurityUtils.getCurrentUsername();
        Long aLong = Long.valueOf(currentUsername);
        dto.setId(aLong);
        return ResponseEntity.ok(ycUserService.putUser(dto));
    }

    /**
     *
     * @param map  ===> map 里参数 password、oldPassword 和 phone 修改密码传password、oldPassword，修改绑定手机号传phone
     * @return
     * @throws Exception
     */
    @ApiOperation("修改用户信息")
    @PutMapping(value = "/sensitive-nformation")
    public ResponseEntity<Object> sensitiveNformation(@RequestBody HashMap<String,String> map) throws Exception {
        ycUserService.sensitiveNformation(map);
        return ResponseEntity.ok("修改成功");
    }

    @ApiOperation("登录授权")
    @PostMapping(value = "/api/login")
    public ResponseEntity<Object> login(@Validated @RequestBody AuthUserDto authUser, HttpServletRequest request) throws Exception {
        // 密码解密
        String password = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey, authUser.getPassword());
        //String password = authUser.getPassword();
        // 查询验证码
        String code = (String) redisUtils.get(authUser.getUuid());
        // 清除验证码
        redisUtils.del(authUser.getUuid());
        if (StringUtils.isBlank(code)) {
            throw new BadRequestException("验证码不存在或已过期");
        }
        if (StringUtils.isBlank(authUser.getCode()) || !authUser.getCode().equalsIgnoreCase(code)) {
            throw new BadRequestException("验证码错误");
        }

        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(authUser.getPhone(), password);

        Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        // 生成令牌
        String token = tokenProvider.createToken(authentication);
        final JwtUserDto jwtUserDto = (JwtUserDto) authentication.getPrincipal();
        // 保存在线信息
        onlineUserService.save(jwtUserDto, token, request);
        // 返回 token 与 用户信息
        Map<String, Object> authInfo = new HashMap<String, Object>(2) {{
            put("token", properties.getTokenStartWith() + token);
            put("user", jwtUserDto);
        }};
        if (loginProperties.isSingleLogin()) {
            //踢掉之前已经登录的token
            onlineUserService.checkLoginOnUser(authUser.getPhone(), token);
        }
        return ResponseEntity.ok(authInfo);
    }


    @ApiOperation("短信登录授权")
    @PostMapping(value = "/api/login-code")
    public ResponseEntity<Object> login(@Validated @RequestBody PhoneCode phoneD,HttpServletRequest request) throws Exception {
        String phone = phoneD.getPhone();
        Integer type = phoneD.getType();
        String code = phoneD.getCode();

        for (SendCodeEnum value : SendCodeEnum.values()) {
            Integer type1 = value.getType();
            if (type1 == type) {
                String key = phone+"_"+value.getVal();
                String smsCode = stringRedisTemplate.opsForValue().get(key);
                if (StringUtils.isBlank(smsCode)) {
                    throw new Exception("验证码不存在,或过期");
                }
                if (StringUtils.equals(code, smsCode)) {
                    stringRedisTemplate.delete(key);
                } else {
                    throw new Exception("验证码不正确");
                }
                SmsCodeAuthenticationToken authenticationToken =
                        new SmsCodeAuthenticationToken(phone,phoneD.getReferralCode());

                Authentication authentication = authenticationManager.authenticate(authenticationToken);
                SecurityContextHolder.getContext().setAuthentication(authentication);
                // 生成令牌
                String token = tokenProvider.createToken(authentication);
                final JwtUserDto jwtUserDto = (JwtUserDto) authentication.getPrincipal();
                // 保存在线信息
                onlineUserService.save(jwtUserDto, token, request);
                // 返回 token 与 用户信息
                Map<String, Object> authInfo = new HashMap<String, Object>(2) {{
                    put("token", properties.getTokenStartWith() + token);
                    put("user", jwtUserDto);
                }};
                if (loginProperties.isSingleLogin()) {
                    //踢掉之前已经登录的token
                    onlineUserService.checkLoginOnUser(phone, token);
                }
                return ResponseEntity.ok(authInfo);
            }
        }
        throw new Exception("类型不正确！");
    }

    @ApiOperation("获取用户信息")
    @GetMapping(value = "/info")
    public ResponseEntity<Object> getUserInfo() {
        return ResponseEntity.ok(SecurityUtils.getCurrentUser());
    }

    @ApiOperation("获取验证码")
    @AnonymousGetMapping(value = "/code")
    public ResponseEntity<Object> getCode() {
        // 获取运算的结果
        Captcha captcha = loginProperties.getCaptcha();
        String uuid = properties.getCodeKey() + IdUtil.simpleUUID();
        // 保存
        redisUtils.set(uuid, captcha.text(), loginProperties.getLoginCode().getExpiration(), TimeUnit.MINUTES);
        // 验证码信息
        Map<String, Object> imgResult = new HashMap<String, Object>(2) {{
            put("img", captcha.toBase64());
            put("uuid", uuid);
        }};
        return ResponseEntity.ok(imgResult);
    }

    @ApiOperation("退出登录")
    @AnonymousDeleteMapping(value = "/logout")
    public ResponseEntity<Object> logout(HttpServletRequest request) {
        onlineUserService.logout(tokenProvider.getToken(request));
        return new ResponseEntity<>(HttpStatus.OK);
    }


}
